<?php

/*
    This file is part of TFCMS.

    TFCMS is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    TFCMS is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with TFCMS. If not, see <http://www.gnu.org/licenses/>.
*/

/*!
 *	\class TFCMSCategory
 *	\file model_tfcms.class.php
 *	\brief TFCMS category class.
 *
 *	TFCMSCategory is used to represent a category.
 */

class TFCMSCategory implements ArrayAccess
	{
		private $category_dirs = array();
		private $category_file = null;
		private $category_id = null;
		private $category_name = null;
		private $category_parent = null;
		private $category_template = null;
		private $category_url = null;
		private $category_urls = array();
		private $children = array();
		private $javascript_paths = array();
		private $special = false;
		private $stylesheet_paths = array();

		/** \brief TFCMSCategory constructor
		 *
		 *	@param string $name Name of category
		 *	@param scalar $id (Optional) ID of category; uniqueness is dependent on the programmer
		 *	@param TFCMSCategory $parent (Optional) Parent category
		 *
		 *	\note If no ID is given, \p $name will be assumed for ID.
		 */
		function __construct($name, $id = null, TFCMSCategory &$parent = null)
			{
				$id = ($id == null) ? $name : $id;
				$this->setId($id);
				$this->setName($name);

				if ($parent != null)
					$parent->addCategory($this);

				TFCMS::getModel()->addCategory($this);
			}

		public function __toString()
			{
				return $this->category_name;
			}

		function addCategory(TFCMSCategory &$category)
			{
				$category->setParent($this);
				$this->children[] = $category;
			}

		function addDirectory(TFCMSDir &$dir)
			{
				$this->category_dirs[] = $dir;
				$dir->setCategory($this);
			}

		/** \brief JavaScript to be listed at the end of the page.
		 *
		 *	@param string $javascripts A semi-colon delimited string of JavaScript file paths.
		 */
		function addJavaScripts($javascripts)
			{
				foreach (explode(";", $javascripts) as $javascript)
					$this->javascript_paths[] = $javascript;
			}

		function addStyleSheets($stylesheets)
			{
				foreach (explode(";", $stylesheets) as $stylesheet)
					$this->stylesheet_paths[] = $stylesheet;
			}

		function addUrls($urls)
			{
				foreach (explode(";", $urls) as $url)
					$this->category_urls[] = $url;
			}

		function directories()
			{
				return (array) $this->category_dirs;
			}

		function hasChildren()
			{
				return (bool) count($this->children);
			}

		function hasDirectories()
			{
				return (bool) count($this->category_dirs);
			}

		function hasFile()
			{
				return (bool) $this->category_file;
			}

		function hasJavascripts()
			{
				return (bool) count($this->javascript_paths);
			}

		function hasLinks()
			{
				return (bool) count($this->category_urls);
			}

		function hasStylesheets()
			{
				return (bool) count($this->stylesheet_paths);
			}

		function hasTemplate()
			{
				return (bool) $this->category_template;
			}

		function hasUrl()
			{
				return (bool) $this->category_url;
			}

		function id($lowercase = false)
			{
				if ($lowercase)
					return strtolower($this->category_id);
				else
					return $this->category_id;
			}

		function isSpecial()
			{
				return (bool) $this->special;
			}

		function javascripts()
			{
				return (array) $this->javascript_paths;
			}

		function makeDefault()
			{
				TFCMS::getModel()->setDefaultCategory($this);
			}

		function makeSpecial()
			{
				$this->special = true;
			}

		function name($lowercase = false)
			{
				if ($lowercase)
					return strtolower($this->category_name);
				else
					return $this->category_name;
			}

		public function offsetExists($offset)
			{
				switch ($offset)
				{
					case "css":
					case "dirs":
					case "file":
					case "id":
					case "js":
					case "links":
					case "name":
					case "parent":
					case "subs":
					case "template":
					case "url":
						return true;
					default:
						return false;
				}
			}

		public function offsetGet($offset)
			{
				if ($offset == "css")
					return (array) $this->stylesheet_paths;

				else if ($offset == "dirs")
					return (array) $this->category_dirs;

				else if ($offset == "file")
					return $this->category_file;

				else if ($offset == "id")
					return $this->category_id;

				else if ($offset == "js")
					return (array) $this->javascript_paths;

				else if ($offset == "links")
					return (array) $this->category_urls;

				else if ($offset == "name")
					return $this->category_name;

				else if ($offset == "parent")
					return $this->category_parent;

				else if ($offset == "subs")
					return (array) $this->children;

				else if ($offset == "template")
					return (string) $this->category_template;

				else if ($offset == "url")
					return $this->category_url;

				return null;
			}

		public function offsetSet($offset, $value)
			{
				if ($offset == "file")
					$this->setFile($value);

				else if ($offset == "id")
					$this->setId($value);

				else if ($offset == "name")
					$this->setName($value);

				else if ($offset == "parent")
					$this->setParent($value);

				else if ($offset == "template")
					$this->setTemplate($value);

				else if ($offset == "url")
					$this->setUrl($value);
			}

		public function offsetUnset($offset)
			{
				// Do not unset any internal variables.
			}

		function parent()
			{
				return $this->category_parent;
			}

		function setFile($file_path)
			{
				$this->category_file = (string) $file_path;
			}

		function setId($id)
			{
				if (is_scalar($id))
					$this->category_id = $id;
				else
					throw new InvalidArgumentException("ID must be of scalar type.");
			}

		function setName($name)
			{
				$this->category_name = (string) $name;
				$this->category_name_lowercase = strtolower($name);
			}

		function setParent(TFCMSCategory &$parent)
			{
				$this->category_parent = $parent;
			}

		function setTemplate($template)
			{
				$this->category_template = (string) $template;
			}

		function setUrl($url)
			{
				$this->category_url = (string) $url;
			}

		function stylesheets()
			{
				return (array) $this->stylesheet_paths;
			}

		function template()
			{
				return $this->category_template;
			}

		function urls()
			{
				return (array) $this->category_urls;
			}
	}

/*!
 *	\class TFCMSDir
 *	\file model_tfcms.class.php
 *	\brief TFCMS directory class.
 *
 *	TFCMSDir is used to represent a directory.
 */

class TFCMSDir implements ArrayAccess
	{
		private $black_list = array(); ///< List of blacklisted files (files to be excluded from menus).
		private $category = null; ///< Category that the directory is a child of.
		private $dir_path; ///< Path to the directory that this class represents.
		private $dir_cache = array(); ///< Cached directory listing.
		private $file_types = array(); ///< File types to include in cache.

		function __construct($path, TFCMSCategory &$category = null)
			{
				$this->dir_path = $path;

				if ($category != null)
					$category->addDirectory($this);

				$this->addFileTypes(implode(";", TFCMS::getModel()->getFileTypes()));
			}

		function __toString()
			{
				return $this->dir_path;
			}

		function addFilesToBlacklist($files)
			{
				foreach (explode(";", $files) as $file)
					$this->black_list[] = $file;
			}

		function addFileTypes($file_types)
			{
				foreach (explode(";", $file_types) as $file_type)
					$this->file_types[] = $file_type;

				$this->update();
			}

		function files()
			{
				return $this->dir_cache;
			}

		function getFileTypes()
			{
				return $this->file_types;
			}

		function hasFile($file)
			{
				return in_array($file, $this->dir_cache);
			}

		function hasFiles()
			{
				return (bool) count($this->dir_cache);
			}

		public function offsetExists($offset)
			{
				return isset($this->dir_cache[$offset]);
			}

		public function offsetGet($offset)
			{
				if ($offset == "category")
					return $this->category;

				if ($offset == "files")
					return $this->dir_cache;

				else if ($offset == "path")
					return $this->dir_path;
			}

		public function offsetSet($offset, $value)
		{
		}

		public function offsetUnset($offset)
		{
			// Do not unset any internal variables.
		}

		function path()
			{
				return $this->dir_path;
			}

		function setCategory(TFCMSCategory &$category)
			{
				$this->category = $category;
			}

		function update()
			{
				if (file_exists($this->dir_path))
				{
					$this->dir_cache = array();

					foreach ($this->file_types as $type)
						$this->dir_cache = array_merge($this->dir_cache, glob($this->dir_path."/*".$type));
				}

				else if (defined('TFCMS_DEBUG'))
					TFCMS::trigger_error("Path ".$this->dir_path." does not exist", E_USER_WARNING);
			}
	}

abstract class TFCMSAbstractModel
	{
		abstract function addCategory(TFCMSCategory &$category);
		abstract function addFileType($file_type);
		abstract function setDefaultCategory(TFCMSCategory &$category);
		abstract function setHomePage($home_page);
	}

/*!
 *	\class TFCMSModel
 *	\file model_tfcms.class.php
 *	\brief TFCMS Model class.
 *
 *	TFCMSModel is used to store and access all content data.
 */

class TFCMSModel extends TFCMSAbstractModel
	{
		private $breadcrumbs = array();
		private $categories = array();
		private $current_category = NULL;
		private $current_file_path = NULL;
		private $current_parent = NULL;
		private $default_category = NULL;
		private $file_types = NULL;
		private $home_page = NULL;

		function __construct()
			{
				define("TFCMS_PATH", substr(TFCMS::pathInfo(), 1));
			}

		/** \brief Used internally by TFCMSCategory, therefore no need to call this yourself.
		 */
		function addCategory(TFCMSCategory &$category)
			{
				$this->categories[] = $category;
			}

		function addFileType($file_type)
			{
				$this->file_types[] = strtolower($file_type);

				if (count($this->categories))
					throw new LogicException("Cannot add file types after creating categories.");
			}

		function currentCategory()
			{
				if (empty($this->breadcrumbs))
					$this->getBreadcrumbs();

				if ($this->current_category != null)
					return $this->current_category;
				else
					return $this->default_category;
			}

		function getBreadcrumbs()
			{
				if (empty($this->breadcrumbs))
				{
					if (empty($this->current_category))
						$this->getCurrentFilePath();

					$this->breadcrumbs[] = $this->current_file_path;
					$this->breadcrumbs[] = $this->current_category;

					$parent = $this->current_category['parent'];
					$category = $this->current_category;

					while ($parent != null)
					{
						$this->breadcrumbs[] = $parent['id'];
						$parent = $parent['parent'];
					}

					$this->breadcrumbs = array_unique($this->breadcrumbs);
					$this->breadcrumbs = array_filter($this->breadcrumbs);
					$this->breadcrumbs = array_reverse($this->breadcrumbs);
				}

				if (empty($this->breadcrumbs))
					$this->breadcrumbs = array($this->default_category);

				return $this->breadcrumbs;
			}

		function getCategories()
			{
				return $this->categories;
			}

		function getCategoryById($id)
			{
				foreach ($this->categories as $cat)
				{
					if ($cat['id'] == $id)
						return $cat;
				}

				return null;
			}

		function getCategoryNames()
			{
				$arr = array();
				foreach ($this->categories as $item)
					$arr[] = $item['name'];

				return $arr;
			}

		function getCurrentFilePath()
			{
				if (strlen(TFCMS_PATH) == 0)
					return $this->home_page;

				$split = explode("/", TFCMS_PATH);
				return $this->getCurrentFileFromCategory(strtolower(@$split[0]), @$split[1]);
			}

		protected function getCurrentFileFromCategory($category, $needle)
			{
				$category = str_replace("_", " ", $category);

				if (empty($this->current_file_path))
				{
					if ($needle)
					{
						foreach ($this->getCategories() as $cat)
						{
							if (strtolower($cat['id']) == $category)
							{
								if ($cat->isSpecial())
									return $this->current_file_path = $cat['file'];
							}

							foreach ($cat->directories() as $dir)
							{
								foreach ($dir->files() as $file)
								{
									$pathinfo = pathinfo($file);
									$pathinfo['filename'] = TFCMSView::removeSpecialCharacters($pathinfo['filename']);
									if ($pathinfo['filename'] == $needle)
									{
										$this->current_category = $cat;
										return $this->current_file_path = $file;
									}
								}
							}
						}

						foreach ($this->getCategories() as $cat)
						{
							if (strtolower($cat['id']) == $category)
							{
								$this->current_category = $cat;
								return $this->current_file_path = $needle;
							}
						}
					}

					else
					{
						foreach ($this->getCategories() as $cat)
						{
							if (strtolower($cat['id']) == $category)
							{
								$this->current_category = $cat;
								return $this->current_file_path = $cat['file'];
							}
						}
					}
				}

				else return $this->current_file_path;
			}

		function getDefaultCategory()
			{
				return $this->default_category;
			}

		function getFileTypes()
			{
				return $this->file_types;
			}

		function getHomePage()
			{
				return $this->home_page;
			}

		function getRootCategories()
			{
				$arr = array();

				foreach ($this->categories as $cat)
				{
					if ($cat['parent'] == null)
						$arr[] = $cat;
				}

				return array_filter($arr);
			}

		function getRootCategoryNames()
			{
				$names = array();

				foreach ($this->getRootCategories() as $category)
				{
					$names[] = $category['name'];
				}

				return array_filter($names);
			}

		function setDefaultCategory(TFCMSCategory &$category)
			{
				$this->default_category = $category;
			}

		function setHomePage($home_page)
			{
				$this->home_page = $home_page;
			}
	}

?>
